// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{io::{SeekFrom, Write}, path::Path, str::FromStr};
use bytes::{Buf, Bytes, BytesMut, TryGetError};
use sha2::{digest::Output, Sha256};
use tokio::{fs::File, io::{self, AsyncReadExt, AsyncSeekExt}};

use crate::{config::pkg_config::{DeployConfig, NoDeployCrossCompileIsolation}, utils::{sha256_from_str, SHA256_STR_LEN}, Arch, Pkg};

use super::BufExt;

#[derive(Debug)]
pub struct PkgStatFile {
    file: File,
    header: Header,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StatSpec {
    pub sha256: Output<Sha256>,
    pub pkg: Pkg,
    pub arch: Arch,
    pub impure: bool,
}

#[derive(Debug, thiserror::Error)]
pub enum PkgStatFileError {
    #[error(transparent)]
    Io(#[from] io::Error),
    #[error("invalid header")]
    InvalidHeader,
    #[error("invalid deploy config")]
    InvalidDeployConfig,
    #[error("unexpected end")]
    UnexpectedEnd(#[from] TryGetError),
}

#[derive(Debug, thiserror::Error)]
pub enum DumpPkgStatError {
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error(transparent)]
    Read(#[from] PkgStatFileError),    
}

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum StatSpecParseError {
    #[error("bad format")]
    BadFormat,
    #[error("bad sha256")]
    BadSha256,
    #[error("bad arch")]
    BadArch,
}

#[derive(Debug, Default)]
struct Header {
    toolchain_section: Section,
    features_section: Section,
    deploy_config_section: Section,
    depends_section: Section,
    host_depends_section: Section,
    runtime_depends_section: Section,
    files_section: Section,
}

#[derive(Debug, Default)]
struct Section {
    offset: u32,
    size: u32,
}

impl PkgStatFile {
    pub async fn open<P: AsRef<Path>>(path: P) -> Result<Self, PkgStatFileError> {
        let mut file = File::open(path).await.map_err(PkgStatFileError::Io)?;
        let mut buffer = BytesMut::with_capacity(size_of::<Header>());
        unsafe { buffer.set_len(size_of::<Header>()); }
        file.read_exact(&mut buffer).await.map_err(|e| match e.kind() {
            io::ErrorKind::UnexpectedEof => PkgStatFileError::InvalidHeader,
            _ => PkgStatFileError::Io(e),
        })?;
        let mut buf = buffer.freeze();
        let toolchain_section = Self::read_section_from_buffer(&mut buf);
        let features_section = Self::read_section_from_buffer(&mut buf);
        let deploy_config_section = Self::read_section_from_buffer(&mut buf);
        let depends_section = Self::read_section_from_buffer(&mut buf);
        let host_depends_section = Self::read_section_from_buffer(&mut buf);
        let runtime_depends_section = Self::read_section_from_buffer(&mut buf);
        let files_section = Self::read_section_from_buffer(&mut buf);

        let header = Header {
            toolchain_section,
            features_section,
            deploy_config_section,
            depends_section,
            host_depends_section,
            runtime_depends_section,
            files_section,
        };
        Ok(Self { file, header })
    }

    fn read_section_from_buffer<T: Buf>(buf: &mut T) -> Section {
        debug_assert!(buf.remaining() >= 2 * size_of::<u32>());
        let offset = buf.get_u32_le();
        let size = buf.get_u32_le();
        Section {
            offset,
            size,
        }
    }

    async fn read_bytes(file: &mut File, section: &Section) -> Result<Bytes, PkgStatFileError> {
        file.seek(SeekFrom::Start(section.offset as u64)).await?;
        let mut buffer = BytesMut::with_capacity(section.size as usize);
        unsafe { buffer.set_len(section.size as usize); }
        file.read_exact(&mut buffer).await.map_err(|e| match e.kind() {
            io::ErrorKind::UnexpectedEof => PkgStatFileError::InvalidHeader,
            _ => PkgStatFileError::Io(e),
        })?;
        let buf = buffer.freeze();
        Ok(buf)
    }

    async fn read_strings(file: &mut File, section: &Section) -> Result<Vec<String>, PkgStatFileError> {
        let mut bytes = Self::read_bytes(file, section).await?;
        let mut strings = Vec::new();
        while bytes.has_remaining() {
            strings.push(bytes.try_get_string()?);
        }
        Ok(strings)
    }

    pub async fn read_toolchain_name(&mut self) -> Result<String, PkgStatFileError> {
        let section = &self.header.toolchain_section;
        let file = &mut self.file;
        let mut bytes = Self::read_bytes(file, section).await?;
        Ok(bytes.try_get_string()?)
    }

    pub async fn read_features(&mut self) -> Result<Vec<String>, PkgStatFileError> {
        let section = &self.header.features_section;
        let file = &mut self.file;
        Self::read_strings(file, section).await
    }

    pub async fn read_deploy_config(&mut self) -> Result<Option<DeployConfig>, PkgStatFileError> {
        let section = &self.header.deploy_config_section;
        let file = &mut self.file;
        let mut bytes = Self::read_bytes(file, section).await?;
        let has_deploy_config = bytes.try_get_bool()?;
        if !has_deploy_config {
            return Ok(None);
        }
        let no_cross_compile_isolation = match bytes.try_get_u8()? {
            0 => NoDeployCrossCompileIsolation::None,
            1 => NoDeployCrossCompileIsolation::All,
            2 => NoDeployCrossCompileIsolation::Paths(bytes.try_get_strings_with_len()?),
            _ => return Err(PkgStatFileError::InvalidDeployConfig),
        };
        let as_host = bytes.try_get_u8()? == 1;
        let script = if bytes.try_get_bool()? {
            Some(bytes.try_get_string()?)
        } else {
            None
        };
        Ok(Some(DeployConfig {
            no_cross_compile_isolation,
            as_host,
            script,
        }))
    }

    pub async fn read_depends(&mut self) -> Result<Vec<String>, PkgStatFileError> {
        let section = &self.header.depends_section;
        let file = &mut self.file;
        Self::read_strings(file, section).await
    }

    pub async fn read_host_depends(&mut self) -> Result<Vec<String>, PkgStatFileError> {
        let section = &self.header.host_depends_section;
        let file = &mut self.file;
        Self::read_strings(file, section).await
    }

    pub async fn read_runtime_depends(&mut self) -> Result<Vec<String>, PkgStatFileError> {
        let section = &self.header.runtime_depends_section;
        let file = &mut self.file;
        Self::read_strings(file, section).await
    }

    pub async fn read_files(&mut self) -> Result<Vec<String>, PkgStatFileError> {
        let section = &self.header.files_section;
        let file = &mut self.file;
        Self::read_strings(file, section).await
    }

    pub async fn dump<W: Write>(&mut self, mut w: W) -> Result<(), DumpPkgStatError> {
        writeln!(w, "toolchain: {}", self.read_toolchain_name().await?)?;
        writeln!(w, "features:")?;
        for s in self.read_features().await? {
            writeln!(w, "  - {}", s)?;
        }
        writeln!(w, "deploy config:")?;
        if let Some(s) = self.read_deploy_config().await? {
            match s.no_cross_compile_isolation {
                NoDeployCrossCompileIsolation::None => writeln!(w, "  - no_cross_compile_isolation: none")?,
                NoDeployCrossCompileIsolation::All => writeln!(w, "  - no_cross_compile_isolation: all")?,
                NoDeployCrossCompileIsolation::Paths(items) => {
                    for s in items {
                        writeln!(w, "  - no_cross_compile_isolation:")?;
                        writeln!(w, "    - {}", s)?;
                    }
                },
            }
            writeln!(w, "  - as_host: {}", s.as_host)?;
            writeln!(w, "  - script: {:?}", s.script)?;
        }
        writeln!(w, "depends:")?;
        for s in self.read_depends().await? {
            writeln!(w, "  - {}", s)?;
        }
        writeln!(w, "host-depends:")?;
        for s in self.read_host_depends().await? {
            writeln!(w, "  - {}", s)?;
        }
        writeln!(w, "runtime-depends:")?;
        for s in self.read_runtime_depends().await? {
            writeln!(w, "  - {}", s)?;
        }
        writeln!(w, "files:")?;
        for s in self.read_files().await? {
            writeln!(w, "  - {}", s)?;
        }
        Ok(())
    }
}

impl FromStr for StatSpec {
    type Err = StatSpecParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.len() < SHA256_STR_LEN {
            return Err(StatSpecParseError::BadFormat);
        }
        let sha256 = sha256_from_str(&s[0..SHA256_STR_LEN])
            .map_err(|_| StatSpecParseError::BadSha256)?;

        let impure = s.ends_with("-impure");
        let pkg_arch_str = if impure {
            &s[SHA256_STR_LEN + 1..s.len() - "-impure".len()]
        } else {
            &s[SHA256_STR_LEN + 1..]
        };
        match pkg_arch_str.rsplit_once('-') {
            Some((s1, s2)) => {
                let pkg = Pkg::from_str(s1)
                    .map_err(|_| StatSpecParseError::BadFormat)?;
                let arch = Arch::from_str(s2)
                    .map_err(|_| StatSpecParseError::BadArch)?;
                Ok(Self {
                    sha256,
                    pkg,
                    arch,
                    impure,
                })
            },
            None => Err(StatSpecParseError::BadFormat),
        }
    }
}
